/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	"fs.h"
#include	<time.h>
#include	<unistd.h>
#include	<string.h>

int
itrunc(int fd, struct filsys *sp, struct inode *ip)
{
	int i, j, k, n, *p;	
	int buf[256], buf1[256], buf2[256];

	if(ip == NULL)
		return -1;
	if(ip->i_mode & (S_IFCHR | S_IFBLK))
		return 0;
	p = ip->i_addr;
	for(i = 0; i < NADDR; i++){ 
		if(p[i]){ 
			if(i >= 10){
				if(bread(fd, p[i], buf, 1))
					return -1;
				for(j = 0; j < 256; j++){
					if(buf[j]){
						if(i >= 11){
							if(bread(fd, buf[j], buf1, 1))
								return -1;
							for(k = 0; k < 256; k++){
								if(buf1[k]){
									if(i >= 12){
										if(bread(fd, buf1[k], buf2, 1))
											return -1;
										for(n = 0; n < 256; n++){
											if(buf2[n])
												bfree(fd, sp, buf2[n]);
										}
									}
									bfree(fd, sp, buf1[k]);
								}
							}
						}
						bfree(fd, sp, buf[j]);
					}
				}
			}
			bfree(fd, sp, p[i]);
			p[i] = 0;
		}
	}
	return 0;
}

int
clrbuf(int fd, int bn)
{
	char buf[BLKSIZ];

	if(bread(fd, bn, buf, 1))
		return -1;
	memset(buf, 0, BLKSIZ);
	return bwrite(fd, bn, buf, 1);
}

int
bread(int fd, int bn, void *buf, int cnt)
{
	if(lseek(fd, bn * BLKSIZ, SEEK_SET) != bn * BLKSIZ)
		return -1;
	return read(fd, buf, cnt * BLKSIZ) == (BLKSIZ * cnt) ? 0 : -1;
}
	
int
bwrite(int fd, int bn, void *buf, int cnt)
{
	if(lseek(fd, bn * BLKSIZ, SEEK_SET) != bn * BLKSIZ)
		return -1;
	return write(fd, buf, cnt * BLKSIZ) == (BLKSIZ * cnt) ? 0 : -1;
}

int
iread(int fd, struct filsys *sp, int ino, struct inode *ip)
{
	int n;
	char buf[BLKSIZ];

	n = NRSVSCT + SPBSIZ + sp->s_ssize + (ino - 1) / NIOPB;
	if(bread(fd, n, buf, 1))
		return -1;
	*ip = *((struct inode *)buf + (ino - 1) % NIOPB);
	return 0;
}

int
iwrite(int fd, struct filsys *sp, int ino, struct inode *ip)
{
	int n;
	char buf[BLKSIZ];

	n = NRSVSCT + SPBSIZ + sp->s_ssize + (ino - 1) / NIOPB;
	if(bread(fd, n, buf, 1))
		return -1;
	*((struct inode *)buf + (ino - 1) % NIOPB) = *ip;
	return bwrite(fd, n, buf, 1);
}

int
isempty(int fd, struct filsys *sp, struct inode *ip)
{
        int cnt;
        int i, j, k;
        struct direct dbuf[NDIRENT];

        cnt = 0;
        for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
                if(log_read(fd, sp, i, dbuf, ip, 1) != 1)
                        return -1;
                for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
                        if(dbuf[k].d_ino && ++cnt > 2)
                                return 0;
                }
        }
        return cnt == 2 ? 1 : 0;
}

int
wddir(int fd, struct filsys *sp, struct inode *ip, struct direct *dp, int flag, int ino)
{
        int i, j, k;
        struct direct dbuf[NDIRENT];

        if(flag){
                for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
                        if(log_read(fd, sp, i, dbuf, ip, 1) != 1)
                                return -1;
                        for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
                                if(strcmp(dbuf[k].d_name, dp->d_name) == 0){
                                        dbuf[k].d_ino = 0;
                                        return log_write(fd, sp, i, dbuf, ip, 1) == 1 ? 0 : -1;
                                }
                        }
                }
                return 0;
        }else{ 
                for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
                        if(log_read(fd, sp, i, dbuf, ip, 1) != 1)
				return -1;
                        for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
                                if(dbuf[k].d_ino == 0){
                                        dbuf[k].d_ino = dp->d_ino;
                                        memmove(dbuf[k].d_name, dp->d_name, DIRSIZ);
                                        return log_write(fd, sp, i, dbuf, ip, 1) == 1 ? 0 : -1;
                                }
                        }
                }
                return ioadd(fd, sp, ino, ip, dp, sizeof(struct direct)) == sizeof(struct direct) ? 0 : -1;
        }
}

int
ioadd(int fd, struct filsys *sp, int ino, struct inode *ip, void *buf, int cnt)
{
        int i, j, n, bn;
        char tmp[BLKSIZ];

	bn = (ip->i_size & ~(BLKSIZ - 1)) >> 10;
	ip->i_size += cnt;
	ip->i_mtime = ip->i_ctime = time(NULL);
	if(iwrite(fd, sp, ino, ip)) 
		return 0;
	i = cnt;
	if((ip->i_size - cnt) % BLKSIZ){
		if(!log_read(fd, sp, bn, tmp, ip, 1))
			return 0;
		if((j = BLKSIZ - (ip->i_size - cnt) % BLKSIZ) > cnt)
			j = cnt;
		memmove(tmp + (ip->i_size - cnt) % BLKSIZ, buf, j);
		if(!log_write(fd, sp, bn, tmp, ip, 1))
			return 0;
		bn++;
		i -= j;
	}
	if(i){
		j = i / BLKSIZ + i % BLKSIZ ? 1 : 0;
		if((n = log_write(fd, sp, bn, buf + cnt - i, ip, j)) < j)
			return cnt - i + n * BLKSIZ;
	}
	return cnt;
}

int
log_read(int fd, struct filsys *sp, int bn, void *buf, struct inode *ip, int cnt)
{
        int i, nb;

        for(i = 0; i < cnt; i++){
                if(nb = bmap(fd, sp, ip, bn + i)){
                        if(bread(fd, nb, buf + i * BLKSIZ, 1))
				break;
		}else
			break;
        }
	return i;
}

int
log_write(int fd, struct filsys *sp, int bn, void *buf, struct inode *ip, int cnt)
{
        int i, nb;

        for(i = 0; i < cnt; i++){
                if(nb = bmap(fd, sp, ip, bn + i)){
                        if(bwrite(fd, nb, buf + i * BLKSIZ, 1))
				break;
		}else
			break;	
        }
	return i;
}

int
ialloc(int fd, struct filsys *sp)
{
	int i, j, s, k;
	char buf[BLKSIZ];
	struct inode *ip;

	for(s = i = sp->s_ssize + NRSVSCT + SPBSIZ, j = i + sp->s_isize; i < j; i++){
		if(bread(fd, i, buf, 1))
			return 0; 
		for(k = 0; k < NIOPB; k++){
			ip = (struct inode *)buf + k;
			if(!ip->i_mode)
				return ((i - s) * NIOPB) + k + 1;
		}
	}
	return 0;
}

int
balloc(int fd, struct filsys *sp)
{
	int i, j, k;
	int buf[256];

	for(i = sp->s_ssize + sp->s_isize + NRSVSCT + SPBSIZ, j = sp->s_fsize; i < j; i += FS_BGRPSZ){
		if(bread(fd, i, buf, 1))
			return 0;
		for(k = 0; k < FS_BGRPSZ - 1; k++){
			if(!buf[k * 2 + 1]){
				if(clrbuf(fd, buf[k * 2]))
					return 0;
				buf[k * 2 + 1] = 1;
				if(bwrite(fd, i, buf, 1))
					return 0;
				return buf[k * 2];
			}
		}
	}
	return 0;
}

int
bfree(int fd, struct filsys *sp, int bn)
{
	int n, k;
	int buf[256];

	k = NRSVSCT + SPBSIZ + sp->s_ssize + sp->s_isize;
	n = ((bn - k) / FS_BGRPSZ) * FS_BGRPSZ + k; 
	if(bread(fd, n, buf, 1))
		return -1;
	for(k = 0; k < FS_BGRPSZ - 1; k++){
		if(buf[k * 2] == bn){
			buf[k * 2 + 1] = 0;
			break;
		}
	}
	if(bwrite(fd, n, buf, 1))
		return -1;
	return 0;
}

int
bmap(int fd, struct filsys *sp, struct inode *ip, int bn)
{
	int i, j, n, nb;
	int buf[256], buf1[256], buf2[256];

	if(bn < NADDR - 3){ 
		if(!ip->i_addr[bn])
			ip->i_addr[bn] = balloc(fd, sp);
		return ip->i_addr[bn];
	}
	nb = 1;
	bn -= NADDR - 3;
	for(i = 3; i > 0; i--){
		nb <<= 8;
		if(bn < nb)
			break;
		bn -= nb;
	}
	if(!i) 
		return 0;
	j = NADDR - i;
	if(!ip->i_addr[j] && !(ip->i_addr[j] = balloc(fd, sp)))
		return 0;	
	nb = ip->i_addr[j];
	if(bread(fd, nb, buf, 1)) 
		return 0;
	switch(i){
	case 1: 
		memmove(buf2, buf, BLKSIZ);
		if(!(n = buf2[bn >> 16]) && !(buf2[bn >> 16] = balloc(fd, sp)))
			return 0;
		if(!n && bwrite(fd, nb, buf2, 1)){ 
			bfree(fd, sp, buf2[bn >> 16]);
			return 0;
		}
		nb = buf2[bn >> 16];
		if(bread(fd, nb, buf1, 1))
			return 0;
		bn &= 0xffff;
	case 2: 
		if(i == 2)
			memmove(buf1, buf, BLKSIZ);
		if(!(n = buf1[bn >> 8]) && !(buf1[bn >> 8] = balloc(fd, sp))) 
			return 0;
		if(!n && bwrite(fd, nb, buf1, 1)){
			bfree(fd, sp, buf1[bn >> 8]);
			return 0;
		}
		nb = buf1[bn >> 8];
		bn &= 0xff;
	case 3: 
		if(i != 3 && bread(fd, nb, buf, 1))
			return 0;
		if(!(n = buf[bn]))
			buf[bn] = balloc(fd, sp);
		if(!n && bwrite(fd, nb, buf, 1)){
			bfree(fd, sp, buf[bn]);
			return 0;
		}
		return buf[bn];
	}
	return 0;
}

int
findfile(int fd, struct filsys *sp, struct inode *ip, char *name)
{
	int i, j, k;
	struct direct dbuf[NDIRENT];

	for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
		if(log_read(fd, sp, i, dbuf, ip, 1) != 1)
			return 0;	
		for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
			if(dbuf[k].d_ino && strcmp(dbuf[k].d_name, name) == 0)
				return dbuf[k].d_ino;
		} 
	}
	return 0;
}
	
char *
have_path(char *path, char *buf)
{
	char *s, *p;

	buf[0] = '\0';
	for(p = path; *p && *p == '/'; p++)
		;
	if(*p){
		for(s = p; *p && *p != '/'; p++)
			;
		memmove(buf, s, p - s >= DIRSIZ ? DIRSIZ : p - s);
		buf[p - s < DIRSIZ ? p - s : DIRSIZ - 1] = '\0';
		while(*p && *p == '/')
			p++;
	}
	return *p ? p : NULL;
}

int
namei(int fd, struct filsys *sp, char *file, char flag, int ino1)
{
	char *p;
	int tm, ino;
	char buf[DIRSIZ];
	struct direct dir;
	struct inode inode, inode1;

	p = file;
	ino = ROOTINO;
	if(*p != '/')
		return 0;
	if(iread(fd, sp, ino, &inode))
		return 0;
	while(p = have_path(p, buf)){
		if(!S_ISDIR(inode.i_mode))
			return 0;
		if(!(ino = findfile(fd, sp, &inode, buf)))
			return 0;
		if(iread(fd, sp, ino, &inode))
			return 0;
	}
	if(!buf[0])
		return ROOTINO;
	if(!S_ISDIR(inode.i_mode))
		return 0;
	switch(flag){
	case 0: 
		return findfile(fd, sp, &inode, buf);
	case 1: 
		tm = ino;
		if(ino = findfile(fd, sp, &inode, buf))
			return 0;
		dir.d_ino = ino1;
		if(dir.d_ino == 0 && !(dir.d_ino = ialloc(fd, sp)))
			return 0;
		memmove(dir.d_name, buf, DIRSIZ);
		if(wddir(fd, sp, &inode, &dir, 0, tm))
			return 0;
		return dir.d_ino;
	case 2: 
		tm = ino;
		if(!(ino = findfile(fd, sp, &inode, buf))) 
			return 0;
		if(iread(fd, sp, ino, &inode1))
			return 0;
		dir.d_ino = ino;
		memmove(dir.d_name, buf, DIRSIZ);
		if(inode1.i_nlink == 1 || (S_ISDIR(inode1.i_mode) && inode1.i_nlink == 2)){
			if(S_ISDIR(inode1.i_mode))
				inode.i_nlink--;	
			itrunc(fd, sp, &inode1);
			memset(&inode1, 0, sizeof(inode1));
		}else
			inode1.i_nlink--;
		if(iwrite(fd, sp, ino, &inode1))
			return 0;	
		if(iwrite(fd, sp, tm, &inode))
			return 0;	
		if(!inode1.i_nlink && wddir(fd, sp, &inode, &dir, 1, tm))
			return 0;
		return ino;
	case 3:
		return ino;
	}
	return 0;
}

int
mc_mknod(int fd, struct filsys *sp, char *pathname, int mode, int dev)
{
	int ino, ino1;
	struct direct dir;
	struct inode inode;
	struct inode inode1;

	if(mode & S_IFDIR){
		if(!(ino = namei(fd, sp, pathname, 1, 0)))
			return -1;	
		if(!(ino1 = namei(fd, sp, pathname, 3, 0)))
			return -1;
		if(iread(fd, sp, ino, &inode))
			return -1;
		if(iread(fd, sp, ino1, &inode1))
			return -1;
		memset(&inode, 0, sizeof(inode));
		inode.i_nlink = 2;
		inode.i_mode = mode;
		inode.i_atime = inode.i_ctime = inode.i_mtime = time(NULL);
		dir.d_ino = ino;
		memmove(dir.d_name, ".", DIRSIZ);
		if(wddir(fd, sp, &inode, &dir, 0, ino))
			return -1;
		dir.d_ino = ino1;
		memmove(dir.d_name, "..", DIRSIZ);
		if(wddir(fd, sp, &inode, &dir, 0, ino))
			return -1;
		inode1.i_nlink++;
		if(iwrite(fd, sp, ino, &inode) || iwrite(fd, sp, ino1, &inode1))
			return -1;
		return 0;
	}else if(mode & S_IFCHR || mode & S_IFBLK){
		if(!(ino = namei(fd, sp, pathname, 1, 0)))
			return -1;
		if(iread(fd, sp, ino, &inode))
			return -1;
		inode.i_nlink = 1;
		inode.i_mode = mode;
		inode.i_addr[0] = dev;
		inode.i_atime = inode.i_ctime = inode.i_mtime = time(NULL);
		return iwrite(fd, sp, ino, &inode);	
	}else
		return -1;
}

int
mc_link(int fd, struct filsys *sp, char *dest, char *source)
{
	int ino;
	struct inode inode; 

	if(!(ino = namei(fd, sp, source, 0, 0)))
		return -1;
	if(iread(fd, sp, ino, &inode))
		return -1;
	inode.i_nlink++;
	inode.i_ctime = time(NULL);
	if(iwrite(fd, sp, ino, &inode))
		return -1;
	return namei(fd, sp, dest, 1, ino) ? 0 : -1;
}
